// ﻿// Copyright (c) Ullrich Praetz - https://github.com/friflo. All rights reserved.
// See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Friflo.Json.Fliox.Mapper.Utils
{
    public static class AttributeUtils {
                
        public static string GetMemberJsonName(MemberInfo memberInfo) {
            foreach (var attr in memberInfo.CustomAttributes) {
                if (attr.AttributeType != typeof(SerializeAttribute)) {
                    continue;
                }
                var arguments   = attr.ConstructorArguments;
                if (arguments.Count >= 1) {
                    var name = (string)arguments[0].Value;
                    if (name != null) {
                        return name;
                    }
                }
                break;
            }
            var jsonNaming = GetJsonNaming(memberInfo.DeclaringType);
            return jsonNaming.PropertyName(memberInfo.Name);
        }

        private static IJsonNaming GetJsonNaming(Type type) {
            if (JsonNamingType(type.CustomAttributes, out var namingType)) {
                return namingType switch {
                    Fliox.NamingPolicyType.Default    => DefaultNaming.Instance,
                    Fliox.NamingPolicyType.CamelCase  => CamelCaseNaming.Instance,
                    Fliox.NamingPolicyType.PascalCase => PascalCaseNaming.Instance,
                    _                               => DefaultNaming.Instance
                };
            }
            return DefaultNaming.Instance;
        }
        
        private static bool JsonNamingType(IEnumerable<CustomAttributeData> attributes, out NamingPolicyType type) {
            foreach (var attr in attributes) {
                if (attr.AttributeType == typeof(NamingPolicyAttribute)) {
                    var arguments   = attr.ConstructorArguments;
                    type = (NamingPolicyType)arguments[0].Value;
                    return true;
                }
            }
            type = default;
            return false;
        }

        public static string CommandName(IEnumerable<CustomAttributeData> attributes) {
            foreach (var attr in attributes) {
                if (attr.AttributeType != typeof(DatabaseCommandAttribute))
                    continue;
                var arguments   = attr.ConstructorArguments;
                return arguments.Count < 1 ? null : (string)arguments[0].Value;
            }
            return null;
        }
        
        public static HandlerType GetHandler(IEnumerable<CustomAttributeData> attributes, out string name) {
            foreach (var attr in attributes) {
                var type = GetHandlerType (attr.AttributeType);
                if (type == HandlerType.None) {
                    continue;
                }
                var arguments   = attr.ConstructorArguments;
                name =  arguments.Count < 1 ? null : (string)arguments[0].Value;
                return type;
            }
            name = null;
            return HandlerType.None;
        }
        
        private static HandlerType GetHandlerType(Type type) {
            if (type == typeof(CommandHandlerAttribute)) {
                return HandlerType.CommandHandler;
            }
            if (type == typeof(MessageHandlerAttribute)) {
                return HandlerType.MessageHandler;
            }
            return HandlerType.None;
        }
        
        internal static void GetMembers(Type type, List<MemberInfo> members) {
            if (type == null || type == typeof(object))
                return;
            var baseType = type.BaseType;
            GetMembers(baseType, members);
            const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
            var memberInfos = type.GetMembers(flags);
            foreach (var member in memberInfos) {
                switch (member) {
                    case PropertyInfo _:
                    case FieldInfo _:
                        members.Add(member);
                        break;
                }
            }
        }

        internal static bool IsAutoGeneratedBackingField(FieldInfo field) {
            foreach (CustomAttributeData attr in field.CustomAttributes) {
                if (attr.AttributeType == typeof(CompilerGeneratedAttribute))
                    return true;
            }
            return false;
        }
        
        public static bool Property(IEnumerable<CustomAttributeData> attributes) {
            foreach (var attr in attributes) {
                if (attr.AttributeType == typeof(SerializeAttribute))
                    return true;
            }
            return false;
        }
        
        internal static bool Ignore(IEnumerable<CustomAttributeData> attributes) {
            foreach (var attr in attributes) {
                if (attr.AttributeType == typeof(IgnoreAttribute))
                    return true;
            }
            return false;
        }
        
        internal static bool IgnoreCollectionInterfacesAttribute(IEnumerable<CustomAttributeData> attributes) {
            foreach (var attr in attributes) {
                if (attr.AttributeType == typeof(IgnoreCollectionInterfacesAttribute))
                    return true;
            }
            return false;
        }

        internal static bool IsRequired(IEnumerable<CustomAttributeData> attributes) {
            foreach (var attr in attributes) {
                if (attr.AttributeType == typeof(RequiredAttribute))
                    return true;
                if (attr.AttributeType == typeof(KeyAttribute))
                    return true;
                // Unity has System.ComponentModel.DataAnnotations.KeyAttribute no available by default
                if (attr.AttributeType.FullName == "System.ComponentModel.DataAnnotations.RequiredAttribute")
                    return true;
            }
            return false;
        }
        
        public static bool IsKey(IEnumerable<CustomAttributeData> attributes) {
            foreach (var attr in attributes) {
                /* if (attr.AttributeType == typeof(PrimaryKeyAttribute))
                    return true; */
                // Unity has System.ComponentModel.DataAnnotations.KeyAttribute no available by default
                if (attr.AttributeType == typeof(KeyAttribute))
                    return true;
            }
            return false;
        }
        
        public static bool IsAutoIncrement(IEnumerable<CustomAttributeData> attributes) {
            foreach (var attr in attributes) {
                if (attr.AttributeType == typeof(AutoIncrementAttribute))
                    return true;
            }
            return false;
        }
        
        // Is given property an indexer
        // see: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/using-indexers
        // The compiler generate a property named 'Item' for indexers
        internal static bool IsIndexerProperty(PropertyInfo property)
        {
            if (property.GetMethod?.GetParameters().Length > 0) {
                return true;
            }
            if (property.SetMethod?.GetParameters().Length > 1) {
                return true;
            }
            return false;
        }
    }
            
    public enum HandlerType
    {
        None            = 0,
        MessageHandler  = 1,
        CommandHandler  = 2,
    }
}